package com.cta.service.impl;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.cta.constant.AreaCodeType;
import com.cta.constant.ProfileType;
import com.cta.dao.SitePersistence;
import com.cta.dao.TariffRecordPersistence;
import com.cta.entity.AreaCodeRate;
import com.cta.entity.DiscountRate;
import com.cta.entity.PBX;
import com.cta.entity.Profile;
import com.cta.entity.TariffAreaCode;
import com.cta.entity.TariffGroup;
import com.cta.entity.TariffRecord;
import com.cta.entity.Trunk;
import com.cta.jms.CDRDefaultMessageListenerContainer;
import com.cta.service.CodeLookupService;
import com.cta.service.ProfileService;
import com.cta.utils.CodeLookupUtils;
import com.cta.utils.SupportLoaderDelegateAdapter;
import com.cta.view.model.ConfigModel;
import com.cta.view.model.ProfileModel;
import com.cta.view.model.discount.DiscountModel;

@Service("codeLookupService")
@Transactional(readOnly = true)
public class CodeLookupServiceImpl implements CodeLookupService {

	private Logger log = LoggerFactory.getLogger(this.getClass());

	@Autowired
	private ProfileService profileService;

	@Autowired
	private TariffRecordPersistence tariffPersistence;

	@Autowired
	private SitePersistence sitePersistence;
	
	@Autowired
	private CDRDefaultMessageListenerContainer cdrProductsMessageListener;
	
	/**
	 * load config for that record
	 * 
	 * @param tariffRecord
	 */
	@Transactional
	public Map<String, ConfigModel> loadConfig(TariffRecord tariffRecord) {
		Map<String, ConfigModel> trunkConfigs = new HashMap<String, ConfigModel>();
		if (tariffRecord != null) {
			// load add group of this
			List<TariffGroup> allTariffGroup = tariffPersistence.listAllTariffGroupWithDiscount(tariffRecord.getId());
			if (allTariffGroup.size() > 0) {
				// this map to collect ids, get group later
				Map<Long, TariffGroup> ids = new HashMap<Long, TariffGroup>(allTariffGroup.size());
				// this map to revert for get original list areacode of group
				Map<Long, List<TariffAreaCode>> groupsAreaCode = new HashMap<Long, List<TariffAreaCode>>(
						allTariffGroup.size());
				Map<Long, List<DiscountModel>> groupsDiscountMap = new HashMap<Long, List<DiscountModel>>(
						allTariffGroup.size());
				for (TariffGroup tariffGroup : allTariffGroup) {
					// init some data
					ids.put(tariffGroup.getId(), tariffGroup);
					groupsAreaCode.put(tariffGroup.getId(), new ArrayList<TariffAreaCode>());
					// temp discount list, merge from group and tariff recode
					List<DiscountRate> discountRates = new ArrayList<DiscountRate>();
					discountRates.addAll(tariffGroup.getDiscountRates());
					discountRates.addAll(tariffRecord.getDiscountRates());
					// convert to discount model and set discount value
					List<DiscountModel> discountModels = new ArrayList<DiscountModel>();
					for (DiscountRate discountRate : discountRates) {
						// will override if group have the holiday discount
						// value
						if (tariffGroup.getHolidayDiscountValue() != 0) {
							discountModels
									.add(DiscountModel.adapTo(discountRate, tariffGroup.getHolidayDiscountValue()));
						} else {
							discountModels.add(DiscountModel.adapTo(discountRate,
									tariffRecord.getHolidayDiscountValue()));
						}
					}
					groupsDiscountMap.put(tariffGroup.getId(), discountModels);
				}
				// get all area code of all group type
				List<TariffAreaCode> listAllTariffAreaCode = tariffPersistence.listAllTariffAreaCodeWithRate(ids
						.keySet());
				if (listAllTariffAreaCode.size() > 0) {
					// group area code to group
					for (TariffAreaCode areaCode : listAllTariffAreaCode) {
						List<TariffAreaCode> list = groupsAreaCode.get(areaCode.getTariffGroup().getId());
						list.add(areaCode);
					}
					//build default group for get telco (should be check default)
					TariffGroup defaultGroup = allTariffGroup.get(0);
					List<TariffAreaCode> listGroup = groupsAreaCode.get(defaultGroup.getId());
					//port to map for lookup
					Map<String, String> telcoMap = new HashMap<String, String>();
					for (TariffAreaCode tariffAreaCode : listGroup) {
						if (tariffAreaCode.getType().equals(AreaCodeType.AREACODE)) {
							telcoMap.put(tariffAreaCode.getAreaCode(), tariffAreaCode.getName());
						}
					}
					
					// process for areacode(template, areacode, discount value)
					Map<Long, TariffAreaCode> templates = new HashMap<Long, TariffAreaCode>();
					for (TariffAreaCode areaCode : listAllTariffAreaCode) {
						try {
							if (areaCode.getType().equals(AreaCodeType.TEMPLATE)) {
								// template should be first in a group
								templates.put(areaCode.getId(), areaCode);
							} else {
								TariffGroup group = ids.get(areaCode.getTariffGroup().getId());
								List<DiscountModel> discountModels = groupsDiscountMap.get(areaCode.getTariffGroup()
										.getId());

								if (areaCode.getAreaCode().indexOf("G") > 0 && areaCode.getAreaCode().indexOf("P") > 0
										&& areaCode.getAreaCode().indexOf("(") > 0
										&& areaCode.getAreaCode().indexOf(")") > 0) {
									// parsing area code as
									// 171G252PE(![8|3])
									// areaCode: 171
									// group: G252
									// P: position (E: end, S: start)
									// areaCode + some areaCode in group
									// 252 which not have the [8|3]
									// value at the end of code
									String code = areaCode.getAreaCode();
									String realAreaCode = code.substring(0, code.indexOf("G"));
									String groupId = code.substring(code.indexOf("G") + 1, code.indexOf("P"));
									String condition = code.substring(code.indexOf("P") + 1, code.indexOf("P") + 2);
									String value = code.substring(code.indexOf("(") + 1, code.indexOf(")"));
									log.info(String
											.format("Parse spec code: %s - areaCode: %s, groupId: %s, condition: %s, value: %s",
													code, realAreaCode, groupId, condition, value));

									// get all area code of a group for compose
									// (171 will compose with group has id 252)
									List<TariffAreaCode> list = groupsAreaCode.get(Long.valueOf(groupId));
									List<TariffAreaCode> tempAreas = new ArrayList<TariffAreaCode>();
									if (list != null && list.size() > 0) {

										// in case not ![8|3]
										// will compose with areacode which is
										// not content 8|3 at position
										if (value.indexOf("!") >= 0) {
											String v = value.substring(1);
											for (TariffAreaCode tariffAreaCode : list) {
												if (tariffAreaCode.getType().equals(AreaCodeType.AREACODE)) {
													if (condition.equals("S")) {
														if (!tariffAreaCode.getAreaCode().startsWith(v)) {
															tempAreas.add(tariffAreaCode);
														}
													} else if (condition.equals("E")) {
														if (!tariffAreaCode.getAreaCode().endsWith(v)) {
															tempAreas.add(tariffAreaCode);
														}
													}
												}
											}
										} else {
											String v = value;
											for (TariffAreaCode tariffAreaCode : list) {
												if (tariffAreaCode.getType().equals(AreaCodeType.AREACODE)) {
													if (condition.equals("S")) {
														if (tariffAreaCode.getAreaCode().startsWith(v)) {
															tempAreas.add(tariffAreaCode);
														}
													} else if (condition.equals("E")) {
														if (tariffAreaCode.getAreaCode().endsWith(v)) {
															tempAreas.add(tariffAreaCode);
														}
													}
												}
											}
										}
										// combined
										for (TariffAreaCode tariffAreaCode : tempAreas) {
											if (tariffAreaCode.getAreaCode().indexOf("[") > 0
													&& tariffAreaCode.getAreaCode().indexOf("]") > 0) {
												String firstCode = tariffAreaCode.getAreaCode().substring(0,
														tariffAreaCode.getAreaCode().indexOf('['));
												String apppendCode = tariffAreaCode.getAreaCode().substring(
														tariffAreaCode.getAreaCode().indexOf('[') + 1,
														tariffAreaCode.getAreaCode().indexOf(']'));

												if (StringUtils.isNotEmpty(firstCode)
														&& StringUtils.isNotEmpty(apppendCode)) {
													String[] aCode = apppendCode.split("\\|");
													for (String c : aCode) {
														ConfigModel config = createConfigModel(tariffAreaCode,
																realAreaCode + firstCode + c, tariffRecord, group,
																templates, discountModels);
														//should get telco from default group
														String telName = telcoMap.get(c);
														if(telName != null){
															config.setTelco(telName);
														} else {
															config.setTelco(config.getAreaName());
														}
														trunkConfigs.put(config.getAreaCode(), config);
													}
												}
											} else {
												ConfigModel config = createConfigModel(tariffAreaCode, realAreaCode
														+ tariffAreaCode.getAreaCode(), tariffRecord, group, templates,
														discountModels);
												config.setTelco(config.getAreaName());
												trunkConfigs.put(config.getAreaCode(), config);
											}
										}
									}
								} else if (areaCode.getAreaCode().indexOf("[") > 0
										&& areaCode.getAreaCode().indexOf("]") > 0) {
									
									
									String firstCode = areaCode.getAreaCode().substring(0,
											areaCode.getAreaCode().indexOf('['));
									String apppendCode = areaCode.getAreaCode().substring(
											areaCode.getAreaCode().indexOf('[') + 1,
											areaCode.getAreaCode().indexOf(']'));
									if (StringUtils.isNotEmpty(firstCode) && StringUtils.isNotEmpty(apppendCode)) {
										String[] aCode = apppendCode.split("\\|");
										for (String c : aCode) {
											ConfigModel config = createConfigModel(areaCode, firstCode + c,
													tariffRecord, group, templates, discountModels);
											//should get telco from default group
											String telName = telcoMap.get(c);
											if(telName != null){
												config.setTelco(telName);
											} else {
												config.setTelco(config.getAreaName());
											}
											trunkConfigs.put(config.getAreaCode(), config);
										}
									}
								} else {
									ConfigModel config = createConfigModel(areaCode, areaCode.getAreaCode(),
											tariffRecord, group, templates, discountModels);
									config.setTelco(config.getAreaName());
									trunkConfigs.put(config.getAreaCode(), config);
								}
							}
						} catch (Exception e) {
							log.error(String.format("Can not parse this code %s", areaCode.getAreaCode()));
						}
					}
				}
			}
		}
		return trunkConfigs;
	}

	/**
	 * this method will call at first start application
	 */
	@Transactional
	public void initConfig() {
		// load all tariff record
		List<TariffRecord> allTariffRecordWidthDiscount = tariffPersistence.listAllTariffRecordWidthDiscount();
		if (allTariffRecordWidthDiscount.size() > 0) {
			for (TariffRecord tariffRecord : allTariffRecordWidthDiscount) {
				Map<String, ConfigModel> tariffConfigs = loadConfig(tariffRecord);
				CodeLookupUtils.addConfig(String.valueOf(tariffRecord.getId()), tariffConfigs);
			}
		} else {
			log.error("there is no config for application, please add, and reload application");
		}
		//init jsm container
		cdrProductsMessageListener.startContainer();
	}

	public void initTrunks() {
		List<Trunk> allTrunks = profileService.findAllTrunks();
		if (allTrunks.size() > 0) {
			for (Trunk trunk : allTrunks) {
				if (trunk.getTrunkNo() != null) {
					CodeLookupUtils.addTrunk(trunk.getTrunkNo(), trunk);
				}
			}
		} else {
			log.error("there is no trunk for application, please add, and reload application");
		}
	}

	public void initPbxs() {
		List<PBX> allPBX = sitePersistence.findAllPBX(new SupportLoaderDelegateAdapter<PBX>() {
			public void load(List<PBX> entities) {
			}
		});
		if (allPBX.size() > 0) {
			for (PBX pbx : allPBX) {
				if (pbx.getPbxNo() != null) {
					loadProfileConfig(pbx.getPbxNo());
				}
			}
		} else {
			log.error("there is no pbx for application, please add, and reload application");
		}
	}

	private AreaCodeRate sortAndfirstRate(List<AreaCodeRate> areaCodeRates) {
		Collections.sort(areaCodeRates, new Comparator<AreaCodeRate>() {
			@Override
			public int compare(AreaCodeRate arg0, AreaCodeRate arg1) {
				return arg0.getEffectedDate().compareTo(arg1.getEffectedDate());
			}
		});
		return areaCodeRates.get(0);
	}

	private ConfigModel createConfigModel(TariffAreaCode areaCode, String realAreaCode, TariffRecord tariffRecord,
			TariffGroup group, Map<Long, TariffAreaCode> templates, List<DiscountModel> discountModels) {
		log.info(realAreaCode);
		// check valid code
		Long.valueOf(realAreaCode);
		ConfigModel config = new ConfigModel();
		config.setAreaCode(realAreaCode);
		config.setAreaName(areaCode.getName());
		config.setTariffId(tariffRecord.getId());
		config.setTariffGroupId(areaCode.getTariffGroup().getId());
		config.setUseValidation(group.isUseValidation());
		config.setShortNumber(group.getShortNumber());
		config.setLongNumber(group.getLongNumber());
		config.setCurrency(group.getCurrency());
		// should be check effected date for this
		if (areaCode.getAreaCodeRates() != null && areaCode.getAreaCodeRates().size() > 0) {
			// get rate from areacode
			config.setAreaCodeRate(sortAndfirstRate(areaCode.getAreaCodeRates()));
		} else {
			// get rate from template
			TariffAreaCode template = templates.get(areaCode.getParentAreaCodeId());
			if (template != null && template.getAreaCodeRates() != null && template.getAreaCodeRates().size() > 0) {
				config.setAreaCodeRate(sortAndfirstRate(template.getAreaCodeRates()));
			}
		}
		config.setFirstBlock(areaCode.getTariffGroup().getFirstBlock());
		config.setSecondBlock(areaCode.getTariffGroup().getSecondBlock());
		config.setDiscountType(areaCode.getTariffGroup().getDiscountType());
		// check this why set here
		if (areaCode.getTariffGroup().getHolidayDiscountValue() > 0) {
			config.setHolidayDiscountValue(areaCode.getTariffGroup().getHolidayDiscountValue());
		} else {
			// get holiday discount value
			// from tariff record
			config.setHolidayDiscountValue(tariffRecord.getHolidayDiscountValue());
		}

		config.setDiscountRates(discountModels);
		return config;
	}

	private void loadProfileConfig(String pbxNo) {
		List<Profile> profileByPBXId = profileService.findProfileByPBXNo(pbxNo);
		if (profileByPBXId.size() > 0) {
			ProfileModel profile = new ProfileModel();
			for (Profile pf : profileByPBXId) {
				if (pf.getProfileType().equals(ProfileType.GENERAL)) {
					if (ProfileModel.DATE_FORMAT_COLUMN.equals(pf.getColumn())) {
						profile.setDateFormat(new SimpleDateFormat(pf.getName()));
					} else if (ProfileModel.TIME_FORMAT_COLUMN.equals(pf.getColumn())) {
						profile.setTimeFormat(new SimpleDateFormat(pf.getName()));
					} else if (ProfileModel.DURATION_FORMAT_COLUMN.equals(pf.getColumn())) {
						profile.setDurationFormat(new SimpleDateFormat(pf.getName()));
					}
				} else if (pf.getProfileType().equals(ProfileType.MAPPING)) {
					profile.getMapProfiles().add(pf);
				}
			}
			CodeLookupUtils.addProfileConfig(pbxNo, profile);
		}
	}
	
	@Transactional
	public void reloadConfig(Long tariffId) throws Exception{
		cdrProductsMessageListener.stop();
		TariffRecord tariffRecord = tariffPersistence.getTariffRecordWidthDiscount(tariffId);
		CodeLookupUtils.addConfig(String.valueOf(tariffId), loadConfig(tariffRecord));
		//Thread.sleep(5000);
		cdrProductsMessageListener.startContainer();
	}
	
	
	public void initServerConfig(){
		CodeLookupUtils.serverConfig = sitePersistence.loadConfig();
	}
}
